// Package conversation implements data that is sent to the view.
package conversation

import (
	"bytes"
	"encoding/json"
	"fmt"

	"gopkg.in/xmlpath.v2"

	"code.google.com/awwwe-browser/auralvm/param"
	"code.google.com/awwwe-browser/data"
	"code.google.com/awwwe-browser/scrub"
	"code.google.com/awwwe-browser/scrub/htmlsoup"
	"code.google.com/awwwe-browser/xpath"
)

const (
	marshallIndent string = " "
	marshallPrefix string = " "
)

// An APICall is used to make a XMLHttpRequest to this server
type APICall struct {
	Package   string
	Object    string
	Method    string
	Arguments map[string]string
}

// An APIOption is a user option.
type APIOption struct {
	Intro      string
	VXMLs      *data.SliceString
	CallYes    APICall
	CallYesNew APICall
	CallNo     APICall
}

// An option for a Conversation.VideoOptions
type VideoOption struct {
	VXMLs  *data.SliceString
	Markup string
    Location string
}

// BuildVideoOption constructs a VideoOption
func BuildVideoOption() VideoOption {
    var v VideoOption
    v.VXMLs = data.NewSliceString(0, 5)
    return v
}

// BuildAPIOption constructs an APIOption
func BuildAPIOption() APIOption {
	var v APIOption
	v.Intro = data.EmptyString
	v.VXMLs = data.NewSliceString(0, 5)
	v.CallYes.Arguments = make(map[string]string)
	v.CallYesNew.Arguments = make(map[string]string)
	v.CallNo.Arguments = make(map[string]string)
	return v
}

type ConversationType int

// ConversationTypeOptions : Options, InitialIndex : A menu of options. A "yes" response must trigger a call or a "no" response must trigger that option's call.
// ConversationTypeLinks : Options : A "yes" and "yes new tab" responses trigger that option's calls.
// ConversationTypeTexts : Texts : Each item in Texts is a paragraph from an article
// conversation.ConversationTypeYesNo : CallYes, CallNo : A "yes" response must triggers a call or a "no" response must trigger a call.
// ConversationTypeFormEnterText : CallYes & TextValue : The response is dictated. A "yes" when asked if ok sends dictation.
// ConversationTypeFormSingleSelect : MenuOption : A "yes" response selects that option and unselects all other options.
// ConversationTypeFormMultipleSelect : MenuOption :  A "yes" response selects that option.
// ConversationTypeFormConfirm : Options : For each form item, tells the user the form element name, what the user entered and asks if the user wants to edit the data.
// ConversationTypeSucceeded : Succeeded : An action succeeded or failed. Client should continue from where it left off.
// ConversationTypeError : : An error. Client should continue from where it left off.
const (
	ConversationTypeNil ConversationType = iota
	ConversationTypeOptions
	ConversationTypeLinks
	ConversationTypeTexts
	ConversationTypeFormEnterText
	ConversationTypeFormSingleSelect
	ConversationTypeFormMultipleSelect
	ConversationTypeYesNo
	ConversationTypeVideos
	ConversationTypeVideo
)

// A conversation is a view model data structure which contains.
// Its Marshal method converts it to a specific json data type for the view based on its Type member.
type Conversation struct {
	Type              ConversationType
	IsMainContent     bool
	Title             string
	PauseOnParse      bool
    OpenWithVideo     bool
	Intro             string            // only spoken at opening
	Header            *data.SliceString // repeated with "restart" command.
	Helpful           string            // "i will say stuff. after i say stuff, you may say back if you want me to repeat the last paragraph""
	MoreHelpful       string            // "you may say back, restart, finish, help."
	Footer            string            // "that is the end of the list of tabs."
	Options           []APIOption
	InitialIndex int
	Texts             []string
	TextValue         param.FormControlText
	MenuOptions       []*param.FormControlOption
	Videos            []VideoOption
    Markup            string
	Name              string
	Action            string
	Method            string
	Required          bool
	CallYes           APICall
	CallNo            APICall
	CallHelp          APICall // always set. is default when no user input.
	CallQuit          APICall // always set. clears tabs.
}

//NewConversation returns a pointer to a new Conversation.
func NewConversation() *Conversation {
	var v Conversation
	v = BuildConversation()
	return &v
}

//BuildConversation constructs a conversation.
func BuildConversation() Conversation {
	var v Conversation
	v.Type = ConversationTypeNil
	v.Header = data.NewSliceString(0, 3)
	v.Options = make([]APIOption, 0, 5)
	v.MenuOptions = make([]*param.FormControlOption, 0, 5)
	v.Texts = make([]string, 0, 5)
    v.Videos = make([]VideoOption, 0, 5)
	v.TextValue = param.BuildFormControlText()
	v.CallYes.Arguments = make(map[string]string)
	v.CallNo.Arguments = make(map[string]string)
	v.CallHelp.Arguments = make(map[string]string)
	// set quit
	v.CallQuit.Package = param.Package
	v.CallQuit.Object = "browser"
	v.CallQuit.Method = "quit"
	v.CallQuit.Arguments = make(map[string]string)
	return v
}

// CopyTo copies one Conversation to another.
func (src *Conversation) CopyTo(dest *Conversation) {
	var option APIOption
	var fco *param.FormControlOption
	var key, value string
	var length, i int
	var text string
    var video VideoOption
	dest.Type = src.Type
	dest.IsMainContent = src.IsMainContent
	dest.Title = src.Title
	dest.PauseOnParse = src.PauseOnParse
	dest.Intro = src.Intro
	length = src.Header.Len()
	for i = 0; i < length; i++ {
		dest.Header.Append(src.Header.AtIndex(i))
	}
	dest.Helpful = src.Helpful
	dest.MoreHelpful = src.MoreHelpful
	dest.Footer = src.Footer
	for _, option = range src.Options {
		dest.Options = append(dest.Options, option)
	}
	for _, fco = range src.MenuOptions {
		dest.MenuOptions = append(dest.MenuOptions, fco)
	}
	for _, text = range src.Texts {
		dest.Texts = append(dest.Texts, text)
	}
    for _, video = range src.Videos {
        dest.Videos = append(dest.Videos, video)
    }
	dest.CallYes.Package = src.CallYes.Package
	dest.CallYes.Object = src.CallYes.Object
	dest.CallYes.Method = src.CallYes.Method
	for key, value = range src.CallYes.Arguments {
		dest.CallYes.Arguments[key] = value
	}
	dest.CallNo.Package = src.CallNo.Package
	dest.CallNo.Object = src.CallNo.Object
	dest.CallNo.Method = src.CallNo.Method
	for key, value = range src.CallNo.Arguments {
		dest.CallNo.Arguments[key] = value
	}
	dest.CallHelp.Package = src.CallHelp.Package
	dest.CallHelp.Object = src.CallHelp.Object
	dest.CallHelp.Method = src.CallHelp.Method
	for key, value = range src.CallHelp.Arguments {
		dest.CallHelp.Arguments[key] = value
	}
	dest.CallQuit.Package = src.CallQuit.Package
	dest.CallQuit.Object = src.CallQuit.Object
	dest.CallQuit.Method = src.CallQuit.Method
	for key, value = range src.CallQuit.Arguments {
		dest.CallQuit.Arguments[key] = value
	}
}

// MatchXpathToTreeNodes returns the scrubber nodes gotten using xpath.
func MatchXpathToTreeNodes(scrubber *scrub.Scrubber, tree *xmlpath.Node, xpath string) []*htmlsoup.SoupNode {
	var ok bool
	var filternode *htmlsoup.SoupNode
	var matches []*htmlsoup.SoupNode
	var ids []int
	var id int
	matches = make([]*htmlsoup.SoupNode, 0, 5)
	ids = _matchXpathToIDs(tree, xpath)
	for _, id = range ids {
		// Use the id to get the node from the scrubber's tree
		filternode, ok = scrubber.GetNodeFromId(id)
		if ok {
			matches = append(matches, filternode)
		}
	}
	return matches
}

// Marshal marshals the conversation in json format using only the needed element.
// Needs to know if
//   this is the first conversation
//   this is an article
//   this opens with video.
func (this *Conversation) Marshal() ([]byte, error) {
	var bfr *bytes.Buffer
	var bb []byte
	var err error
	bfr = bytes.NewBuffer(data.EmptyBytes)
	bfr.WriteString("{\n")
	bfr.WriteString(` "Type": `)
	bb, err = json.MarshalIndent(this.Type, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "Intro": `)
	bb, err = json.MarshalIndent(this.Intro, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "Header": `)
	bb, err = json.MarshalIndent(this.Header, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "Helpful": `)
	bb, err = json.MarshalIndent(this.Helpful, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "MoreHelpful": `)
	bb, err = json.MarshalIndent(this.MoreHelpful, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "Footer": `)
	bb, err = json.MarshalIndent(this.Footer, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	switch this.Type {
	case ConversationTypeOptions, ConversationTypeLinks:
		bfr.WriteString(` "InitialIndex": `)
		bb, err = json.MarshalIndent(this.InitialIndex, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
		bfr.WriteString(` "Options": `)
		bb, err = json.MarshalIndent(this.Options, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeTexts:
		bfr.WriteString(` "InitialIndex": `)
		bb, err = json.MarshalIndent(this.InitialIndex, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
		bfr.WriteString(` "Texts": `)
		bb, err = json.MarshalIndent(this.Texts, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeYesNo:
		bfr.WriteString(` "CallYes": `)
		bb, err = json.MarshalIndent(this.CallYes, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
		bfr.WriteString(` "CallNo": `)
		bb, err = json.MarshalIndent(this.CallNo, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeFormSingleSelect, ConversationTypeFormMultipleSelect:
		bfr.WriteString(` "InitialIndex": `)
		bb, err = json.MarshalIndent(this.InitialIndex, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
		bfr.WriteString(` "MenuOptions": `)
		bb, err = json.MarshalIndent(this.MenuOptions, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeFormEnterText:
		bfr.WriteString(` "TextValue": `)
		bb, err = json.MarshalIndent(this.TextValue, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeVideos:
		bfr.WriteString(` "InitialIndex": `)
		bb, err = json.MarshalIndent(this.InitialIndex, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
		bfr.WriteString(` "Videos": `)
		bb, err = json.MarshalIndent(this.Videos, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	case ConversationTypeVideo:
		bfr.WriteString(` "Markup": `)
		bb, err = json.MarshalIndent(this.Markup, marshallPrefix, marshallIndent)
		if err != nil {
			return bfr.Bytes(), err
		}
		bfr.Write(bb)
		bfr.WriteString(",\n")
	}
	bfr.WriteString(` "CallHelp": `)
	bb, err = json.MarshalIndent(this.CallHelp, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString(",\n")
	bfr.WriteString(` "CallQuit": `)
	bb, err = json.MarshalIndent(this.CallQuit, marshallPrefix, marshallIndent)
	if err != nil {
		return bfr.Bytes(), err
	}
	bfr.Write(bb)
	bfr.WriteString("\n}\n")
	return bfr.Bytes(), nil
}

// returns the IDs of the nodes found in tree with xpathexp.
func _matchXpathToIDs(tree *xmlpath.Node, xpathexp string) []int {
	var node, node2 *xmlpath.Node
	var nodes []*xmlpath.Node
	var idString string
	var idInt int
	var ok bool
	var ids []int
	var attr string
	var compiledPath *xmlpath.Path
	var iter *xmlpath.Iter
	var err error
	ids = make([]int, 0, 5)
	nodes = xpath.PathNodesString(xpathexp, tree)
	attr = fmt.Sprintf("@%s", data.AWWWEAttributeId)
	compiledPath, err = xmlpath.Compile(attr)
	if err == nil {
		for _, node = range nodes {
			iter = compiledPath.Iter(node)
			if iter.Next() {
				node2 = iter.Node()
				idString = node2.String()
				idInt, ok = data.StringToInt(idString)
				if ok {
					ids = append(ids, idInt)
				}
			}
		}
	}
	return ids
}
